//
//  NSObject+LYAdd.m
//  LYDevelopKit
//
//  Created by 李阳 on 2018/5/26.
//

#import "NSObject+LYAdd.h"
#import "LYNSMacro.h"
#import <pthread.h>

static inline dispatch_time_t dTimeDelay(NSTimeInterval time)
{
    int64_t delta = (int64_t)(NSEC_PER_SEC * time);
    return dispatch_time(DISPATCH_TIME_NOW, delta);
}

LYSYNTH_DUMMY_CLASS(NSObject_LYAdd)


@interface _LYNotifyBlockTarget : NSObject

@property (nonatomic, copy) void(^block)(NSNotification *notify);

- (instancetype)initWithBlock:(void(^)(NSNotification *notify))block;

@end

@implementation _LYNotifyBlockTarget

- (instancetype)initWithBlock:(void (^)(NSNotification *))block
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
    self.block = block;
    return self;
}

- (void)receiveNotification:(NSNotification *)notify
{
    if (!self.block)
    {
        return;
    }
    
    self.block(notify);
}

@end


@implementation NSObject (LYAdd)


#pragma mark Notification
/// 订阅通知
- (void)subscribeNotificationNamed:(NSString *)name block:(void (^)(NSNotification *notify))block
{
    if (!name.length || !block)
    {
        return;
    }
    _LYNotifyBlockTarget *target = [[_LYNotifyBlockTarget alloc]initWithBlock:block];
    NSMutableDictionary *dic = [self _ly_notifyBlocksTargetsInfo];
    NSMutableArray *arr = dic[name];
    if (!arr)
    {
        arr = [NSMutableArray array];
        dic[name] = arr;
    }
    [arr addObject:target];
    [[NSNotificationCenter defaultCenter]addObserver:target selector:@selector(receiveNotification:) name:name object:nil];
}
/// 取消订阅通知
- (void)unsubscribeNotificationWithName:(NSString *)name
{
    if (!name.length)
    {
        return;
    }
    NSMutableDictionary *dic = [self _ly_notifyBlocksTargetsInfo];
    NSMutableArray *arr = dic[name];
    [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [[NSNotificationCenter defaultCenter]removeObserver:obj forKeyPath:name];
    }];
    [dic removeObjectForKey:name];
}
/// 取消订阅过的所有通知
- (void)unsubscribeAllNotifications
{
    NSMutableDictionary *dic = [self _ly_notifyBlocksTargetsInfo];
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray *arr, BOOL *stop) {
        [arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [[NSNotificationCenter defaultCenter]removeObserver:obj forKeyPath:key];
        }];
    }];
    
    [dic removeAllObjects];
}


- (NSMutableDictionary *)_ly_notifyBlocksTargetsInfo
{
    NSMutableDictionary *targets = objc_getAssociatedObject(self, _cmd);
    if (!targets)
    {
        targets = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, _cmd, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return targets;
}
#pragma mark GCD

/**
 *  @brief  异步执行代码块
 *
 *  @param block 代码块
 */
- (void)performAsynchronous:(void(^)(void))block
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, block);
}
/**
 *  @brief  GCD主线程执行代码块
 *
 *  @param block 代码块
 *  @param shouldWait  是否同步请求
 */
- (void)performOnMainThread:(void(^)(void))block wait:(BOOL)shouldWait
{
    if (shouldWait)
    {
        // Synchronous
        dispatch_sync(dispatch_get_main_queue(), block);
    }
    else
    {
        // Asynchronous
        dispatch_async(dispatch_get_main_queue(), block);
    }
}
/**
 *  @brief  延迟执行代码块
 *
 *  @param seconds 延迟时间 秒
 *  @param block   代码块
 */
- (void)performAfter:(NSTimeInterval)seconds block:(void(^)(void))block
{
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);
    //    dispatch_after(popTime, dispatch_get_current_queue(), block);
    dispatch_after(popTime, dispatch_get_main_queue(), block);
}

#pragma mark Block

- (id)performBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay
{
    if (!block) return nil;
    
    __block BOOL cancelled = NO;
    
    void (^wrappingBlock)(BOOL) = ^(BOOL cancel) {
        if (cancel)
        {
            cancelled = YES;
            return;
        }
        if (!cancelled) block();
    };
    
    wrappingBlock = [wrappingBlock copy];
    
    dispatch_after(dTimeDelay(delay), dispatch_get_main_queue(), ^{  wrappingBlock(NO); });
    
    return wrappingBlock;
}
- (id)performBlock:(void (^)(id arg))block withObject:(id)anObject afterDelay:(NSTimeInterval)delay
{
    if (!block) return nil;
    
    __block BOOL cancelled = NO;
    
    void (^wrappingBlock)(BOOL, id) = ^(BOOL cancel, id arg) {
        if (cancel) {
            cancelled = YES;
            return;
        }
        if (!cancelled) block(arg);
    };
    
    wrappingBlock = [wrappingBlock copy];
    
    dispatch_after(dTimeDelay(delay), dispatch_get_main_queue(), ^{  wrappingBlock(NO, anObject); });
    
    return wrappingBlock;
}
- (void)cancelBlock:(id)block
{
    if (!block) return;
    void (^aWrappingBlock)(BOOL) = (void(^)(BOOL))block;
    aWrappingBlock(YES);
}

@end




#pragma mark - Inject

typedef struct BLSpecialProtocol {
    __unsafe_unretained Protocol *protocol;
    Class containerClass;
    BOOL ready;
} BLSpecialProtocol;

static BLSpecialProtocol * restrict bl_specialProtocols = NULL;
static size_t bl_specialProtocolCount = 0;
static size_t bl_specialProtocolCapacity = 0;
static size_t bl_specialProtocolsReady = 0;
static pthread_mutex_t bl_specialProtocolsLock = PTHREAD_MUTEX_INITIALIZER;
static dispatch_semaphore_t bl_specialProtocolsSemaphore;

BOOL bl_loadSpecialProtocol (Protocol *protocol, Class containerClass) {
    @autoreleasepool {
        NSCParameterAssert(protocol != nil);
        if (pthread_mutex_lock(&bl_specialProtocolsLock) != 0) {
            fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
            return NO;
        }
        
        if (bl_specialProtocolCount == SIZE_MAX) {
            pthread_mutex_unlock(&bl_specialProtocolsLock);
            return NO;
        }
        
        if (bl_specialProtocolCount >= bl_specialProtocolCapacity) {
            size_t newCapacity;
            if (bl_specialProtocolCapacity == 0)
                newCapacity = 1;
            else {
                newCapacity = bl_specialProtocolCapacity << 1;
                
                if (newCapacity < bl_specialProtocolCapacity) {
                    newCapacity = SIZE_MAX;
                    
                    if (newCapacity <= bl_specialProtocolCapacity) {
                        pthread_mutex_unlock(&bl_specialProtocolsLock);
                        return NO;
                    }
                }
            }
            
            void * restrict ptr = realloc(bl_specialProtocols, sizeof(*bl_specialProtocols) * newCapacity);
            if (!ptr) {
                pthread_mutex_unlock(&bl_specialProtocolsLock);
                return NO;
            }
            
            bl_specialProtocols = ptr;
            bl_specialProtocolCapacity = newCapacity;
        }
        assert(bl_specialProtocolCount < bl_specialProtocolCapacity);
        
#ifndef __clang_analyzer__
        
        bl_specialProtocols[bl_specialProtocolCount] = (BLSpecialProtocol){
            .protocol = protocol,
            .containerClass = containerClass,
            .ready = NO,
        };
#endif
        
        ++bl_specialProtocolCount;
        pthread_mutex_unlock(&bl_specialProtocolsLock);
    }
    
    return YES;
}

static void bl_orderSpecialProtocols(void) {
    qsort_b(bl_specialProtocols, bl_specialProtocolCount, sizeof(BLSpecialProtocol), ^(const void *a, const void *b){
        if (a == b)
            return 0;
        
        const BLSpecialProtocol *protoA = a;
        const BLSpecialProtocol *protoB = b;
        
        int (^protocolInjectionPriority)(const BLSpecialProtocol *) = ^(const BLSpecialProtocol *specialProtocol){
            int runningTotal = 0;
            
            for (size_t i = 0;i < bl_specialProtocolCount;++i) {
                if (specialProtocol == bl_specialProtocols + i)
                    continue;
                
                if (protocol_conformsToProtocol(specialProtocol->protocol, bl_specialProtocols[i].protocol))
                    runningTotal++;
            }
            
            return runningTotal;
        };
        return protocolInjectionPriority(protoB) - protocolInjectionPriority(protoA);
    });
}

void bl_specialProtocolReadyForInjection (Protocol *protocol) {
    @autoreleasepool {
        NSCParameterAssert(protocol != nil);
        
        if (pthread_mutex_lock(&bl_specialProtocolsLock) != 0) {
            fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
            return;
        }
        for (size_t i = 0;i < bl_specialProtocolCount;++i) {
            if (bl_specialProtocols[i].protocol == protocol) {
                if (!bl_specialProtocols[i].ready) {
                    bl_specialProtocols[i].ready = YES;
                    assert(bl_specialProtocolsReady < bl_specialProtocolCount);
                    if (++bl_specialProtocolsReady == bl_specialProtocolCount)
                        bl_orderSpecialProtocols();
                }
                
                break;
            }
        }
        
        pthread_mutex_unlock(&bl_specialProtocolsLock);
    }
}


static void bl_injectConcreteProtocolInjectMethod(Class containerClass, Class pairClass) {
    unsigned imethodCount = 0;
    Method *imethodList = class_copyMethodList(containerClass, &imethodCount);
    for (unsigned methodIndex = 0;methodIndex < imethodCount;++methodIndex) {
        Method method = imethodList[methodIndex];
        SEL selector = method_getName(method);
        IMP imp = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(pairClass, selector, imp, types);
    }
    free(imethodList); imethodList = NULL;
    (void)[containerClass class];
    
    unsigned cmethodCount = 0;
    Method *cmethodList = class_copyMethodList(object_getClass(containerClass), &cmethodCount);
    
    Class metaclass = object_getClass(pairClass);
    for (unsigned methodIndex = 0;methodIndex < cmethodCount;++methodIndex) {
        Method method = cmethodList[methodIndex];
        SEL selector = method_getName(method);
        
        if (selector == @selector(initialize)) {
            continue;
        }
        
        IMP imp = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(metaclass, selector, imp, types);
    }
    
    free(cmethodList); cmethodList = NULL;
    (void)[containerClass class];
}

static NSArray * bl_injectMethod(id object) {
    NSMutableArray *bl_matchSpecialProtocolsToClass = @[].mutableCopy;
    for (size_t i = 0;i < bl_specialProtocolCount;++i) {
        @autoreleasepool {
            Protocol *protocol = bl_specialProtocols[i].protocol;
            if (!class_conformsToProtocol([object class], protocol)) {
                continue;
            }
            [bl_matchSpecialProtocolsToClass addObject:[NSValue value:&bl_specialProtocols[i] withObjCType:@encode(struct BLSpecialProtocol)]];
        }
    }
    
    if(!bl_matchSpecialProtocolsToClass.count) {
        return nil;
    }
    
    struct BLSpecialProtocol protocol;
    for(NSValue *value in bl_matchSpecialProtocolsToClass) {
        [value getValue:&protocol];
        bl_injectConcreteProtocolInjectMethod(protocol.containerClass, [object class]);
    }
    return bl_matchSpecialProtocolsToClass.copy;
}

static bool bl_resolveMethodForObject(id object) {
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            bl_specialProtocolsSemaphore = dispatch_semaphore_create(1);
        });
        
        dispatch_semaphore_wait(bl_specialProtocolsSemaphore, DISPATCH_TIME_FOREVER);
        
        // 处理继承自有注入的父类.
        Class currentClass = [object class];
        NSArray *matchSpecialProtocolsToClass = nil;
        do {
            NSArray *protocols = bl_injectMethod(currentClass);
            if(!matchSpecialProtocolsToClass) {
                matchSpecialProtocolsToClass = protocols;
            }
        }while((currentClass = class_getSuperclass(currentClass)));
        
        if(!matchSpecialProtocolsToClass.count) {
            dispatch_semaphore_signal(bl_specialProtocolsSemaphore);
            return nil;
        }
        
        dispatch_semaphore_signal(bl_specialProtocolsSemaphore);
        return YES;
    }
}

BOOL bl_addConcreteProtocol (Protocol *protocol, Class containerClass)
{
    return bl_loadSpecialProtocol(protocol, containerClass);
}

void bl_loadConcreteProtocol (Protocol *protocol)
{
    bl_specialProtocolReadyForInjection(protocol);
}

@interface NSObject(BLInjecting)

@end

@implementation NSObject(BLInjecting)

+ (void)load
{
    [self bl_swizzleClassMethod:@selector(resolveInstanceMethod:)
                with:@selector(blinjecting_resolveInstanceMethod:)];
    [self bl_swizzleClassMethod:@selector(resolveClassMethod:)
                with:@selector(blinjecting_resolveClassMethod:)];
}

+ (BOOL)bl_swizzleClassMethod:(SEL)originalSel with:(SEL)newSel
{
    Class class = object_getClass(self);
    Method originalMethod = class_getInstanceMethod(class, originalSel);
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!originalMethod || !newMethod) return NO;
    method_exchangeImplementations(originalMethod, newMethod);
    return YES;
}

+ (BOOL)blinjecting_resolveClassMethod:(SEL)sel
{
    if(bl_resolveMethodForObject(self))
    {
        return YES;
    }
    return [self blinjecting_resolveClassMethod:sel];
}

+ (BOOL)blinjecting_resolveInstanceMethod:(SEL)sel
{
    if(bl_resolveMethodForObject(self))
    {
        return YES;
    }
    return [self blinjecting_resolveInstanceMethod:sel];
}

@end
